﻿using System;
using System.Diagnostics;
using AzureStorageMapper.Configurations;
using AzureStorageMapper.Criterias;
using AzureStorageMapper.Criterias.TableStorage;
using AzureStorageMapper.TableStorage.ActionModes;
using AzureStorageMapper.TableStorage.RESTCommunicate;
using AzureStorageMapper.TableStorage.Utils;

namespace AzureStorageMapper.TableStorage
{
    public class TableContext<T> where T : TableEntity
    {
        private string _accountName;
        private string _accountKey;

        private TalkToTableStorage TalkToTableStorageProxy
        {
            get
            {
                if (!string.IsNullOrWhiteSpace(this._accountName) && !string.IsNullOrWhiteSpace(this._accountKey))
                {
                    return new TalkToTableStorage(this._accountName, this._accountKey);
                }
                else
                {
                    CredentialElement credential = ConfigurationUtils.GetTableStroageCredential();
                    return new TalkToTableStorage(credential.AccountName, credential.AccountKey);
                }
            }
        }

        public TableContext()
        {

        }

        public TableContext(string accountName, string accountKey)
        {
            this._accountName = accountName;
            this._accountKey = accountKey;
        }

        public void CreateTable()
        {
            TableInfo tInfo = TableUtilities.GetTableInfo(typeof(T));
            TalkToTableStorageProxy.CreateTable(tInfo);
        }

        public void DeleteTable()
        {
            TableInfo tInfo = TableUtilities.GetTableInfo(typeof(T));
            TalkToTableStorageProxy.DeleteTable(tInfo);
        }

        public T FindEntity(string partitionKey, string rowKey)
        {
            TableInfo tInfo = TableUtilities.GetTableInfo(typeof(T));
            return TalkToTableStorageProxy.QueryEntity<T>(tInfo.Name, partitionKey, rowKey);
        }

        public QueryEntitiesResult<T> FindEntities(AbstractQuery query = null,
                                                  int? topCount = null,
                                                  ContinuationQuery continuation = null)
        {
            TableInfo tInfo = TableUtilities.GetTableInfo(typeof(T));

            string filterStr = null;

            if (query != null)
            {
                filterStr = TableQueryUtils.GetStringQuery(query);
                Trace.TraceInformation("Filtering: '{0}' <== '{1}'", tInfo.Name, filterStr);
            }

            string cQuery = null;

            if (continuation != null)
            {
                cQuery = TableQueryUtils.GetStringQuery(continuation);
                Trace.TraceInformation("Continuation: '{0}' <== '{1}'", tInfo.Name, cQuery);
            }

            if (topCount.HasValue)
            {
                Trace.TraceInformation("Select top '{0}' entities ...", topCount.Value);
            }

            return TalkToTableStorageProxy.QueryEntities<T>(tInfo.Name, cQuery, filterStr, topCount);
        }

        public void Save(T obj, SaveMode mode = SaveMode.Insert)
        {
            EntityInfo eInfo = TableUtilities.GetEntityInfo(obj);
            TalkToTableStorageProxy.InsertEntity(eInfo, mode);
        }

        public void Update(T obj, UpdateMode mode = UpdateMode.Update)
        {
            EntityInfo eInfo = TableUtilities.GetEntityInfo(obj);
            TalkToTableStorageProxy.UpdateEntity(eInfo, mode);
        }

        public void Delete(T obj)
        {
            EntityInfo eInfo = TableUtilities.GetEntityInfo(obj);
            TalkToTableStorageProxy.DeleteEntity(eInfo.TableName, eInfo.PartitionKey, eInfo.RowKey);
        }

        public IAsyncResult BeginCreateTable(AsyncCallback callback)
        {
            Action action = this.CreateTable;
            return action.BeginInvoke(callback, action);
        }

        public void EndCreateTable(IAsyncResult result)
        {
            this.EndAction(result);
        }

        public IAsyncResult BeginDeleteTable(AsyncCallback callback)
        {
            Action action = this.DeleteTable;
            return action.BeginInvoke(callback, action);
        }

        public void EndDeleteTable(IAsyncResult result)
        {
            this.EndAction(result);
        }

        public IAsyncResult BeginFindEntity(string partitionKey, string rowKey, AsyncCallback callback = null)
        {
            Func<string, string, T> action = this.FindEntity;
            return action.BeginInvoke(partitionKey, rowKey, callback, action);
        }

        public void EndFindEntity(IAsyncResult result)
        {
            this.EndAction(result);
        }

        public IAsyncResult BeginFindEntities(AbstractQuery query = null,
                                              int? topCount = null,
                                              ContinuationQuery continuation = null,
                                              AsyncCallback callback = null)
        {
            Func<AbstractQuery, int?, ContinuationQuery, QueryEntitiesResult<T>> action = this.FindEntities;
            return action.BeginInvoke(query, topCount, continuation, callback, action);
        }

        public void EndFindEntities(IAsyncResult result)
        {
            this.EndAction(result);
        }

        public IAsyncResult BeginSave(T obj, SaveMode mode = SaveMode.Insert, AsyncCallback callback = null)
        {
            Action<T, SaveMode> action = this.Save;
            return action.BeginInvoke(obj, mode, callback, action);
        }

        public void EndSave(IAsyncResult result)
        {
            Action<T, SaveMode> action = (Action<T, SaveMode>)result.AsyncState;
            action.EndInvoke(result);
        }

        public IAsyncResult BeginUpdate(T obj, UpdateMode mode = UpdateMode.Update, AsyncCallback callback = null)
        {
            Action<T, UpdateMode> action = this.Update;
            return action.BeginInvoke(obj, mode, callback, action);
        }

        public void EndUpdate(IAsyncResult result)
        {
            Action<T, UpdateMode> action = (Action<T, UpdateMode>)result.AsyncState;
            action.EndInvoke(result);
        }

        public IAsyncResult BeginDelete(T obj, AsyncCallback callback = null)
        {
            Action<T> action = this.Delete;
            return action.BeginInvoke(obj, callback, action);
        }

        public void EndDelete(IAsyncResult result)
        {
            Action<T> action = (Action<T>)result.AsyncState;
            action.EndInvoke(result);
        }

        private void EndAction(IAsyncResult result)
        {
            Action action = (Action)result.AsyncState;
            action.EndInvoke(result);
        }
    }
}
